﻿using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text.RegularExpressions;

namespace Framework.Core.Com
{
    static class ComWrapperExtension
    {
        public static Type GetRcwType(this object comObject)
        {
            var iDispatch = comObject as IDispatch;
            uint typeCount = 0;

            if (iDispatch == null)
            {
                return null;
            }

            iDispatch.GetTypeInfoCount(ref typeCount);

            if (typeCount <= 0)
            {
                return null;
            }
            var intPtr = IntPtr.Zero;
            iDispatch.GetTypeInfo(0, 0, ref intPtr);
            var typeInfo = (ITypeInfo)Marshal.GetTypedObjectForIUnknown(intPtr, typeof(ITypeInfo));
            Marshal.Release(intPtr);

            ITypeLib typeLib;
            int typeIndex;

            var typeName = Marshal.GetTypeInfoName(typeInfo);
            typeName = Regex.Replace(typeName, "^_(.*)", "$1Class");

            typeInfo.GetContainingTypeLib(out typeLib, out typeIndex);
            var moduleName = Marshal.GetTypeLibName(typeLib);

            var assemblyName = new AssemblyName(String.Format("Interop.{0}", moduleName));

            var asm = GetOrLoadAssembly(assemblyName);

            var result = asm.GetType(String.Format("{0}.{1}", moduleName, typeName));
            return result;
        }

        private static Assembly GetOrLoadAssembly(AssemblyName assemblyName)
        {
            return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == assemblyName.FullName) ??
                   Assembly.Load(assemblyName);
        }

        public static object CreateWrapper(this object comObject)
        {
            return Marshal.CreateWrapperOfType(comObject, comObject.GetRcwType());
        }

        private static bool IsSystemComObject(this object o)
        {
            return o.GetType() == Type.GetType("System.__ComObject");
        }

        public static object AsRealWrapperComType(this object o)
        {
            if (o.GetType().IsCOMObject && o.IsSystemComObject()) o = CreateWrapper(o);
            return o;
        }
    }
}
